# load required libraries

# to use harry potter dataset
# devtools::install_github("bradleyboehmke/harrypotter")
# devtools::install_github("quanteda/quanteda.sentiment")
# devtools::install_github("quanteda/quanteda.corpora")

library(quanteda)
library(readtext)
library(corpus)
library(tidyverse)
library(stringr)
library(tidytext)
library(harrypotter)
library(dplyr)
library(quanteda.sentiment)
library(vader)
library(caret)
library(reshape2)


require(quanteda)
require(quanteda.corpora)
require(quanteda.sentiment)
#library("quanteda", warn.conflicts = FALSE, quietly = TRUE)

1. Step: Load Data & Lexicons

# load datasets
reviews <- readRDS(file="datasets/red_review.rds")
twitter <- readRDS(file="datasets/red_twitter.rds")
parlvote <- readRDS(file="datasets/red_parl_vote.rds")

# load lexicons
afinn <- data_dictionary_AFINN
lsd <- data_dictionary_LSD2015
reviews
twitter
parlvote

2. Step: Perform Sentiment Analysis

Normalize Scores

# DATA NORMALIZATION
# Normalize data via minimun/maximum normalization, either by scaling values from 0 to 1 or -1 to 1
# 
# Arg:
#   x: input values (e.g. column of data frame)
#   
# Returns: 
#   normalized data


# min/max normalization from 0 to 1
normalize <- function(x, na.rm = TRUE){
  return((x-min(x)) / (max(x)-min(x)))}

# min/max normalization from -1 to 1
normalize2 <- function(x, na.rm = TRUE){
  return(2* ((x - min(x)) / (max(x)-min(x)))-1)}

Calculate Sentiment Scores

# Calculate sentiment scores for different lexicons and input data frames
# 
# Arg:
#   data: input data frame
#   lexicons: names of lexicons that should be used for sentiment scoring
#   normalize: TRUE or FALSE, to optionally normalize sentiment scores
#   
# Returns: 
#   Data frame with (normalized) sentiment sores for chosen lexicons

get_sentiment <- function(df, lexicons, normalize = TRUE){
  
  # for each lexicon, get sentiment scores and save scores in new column of data frame
  for(lex in lexicons){
    
    if(lex == "afinn"){
    df$afinn <- round(textstat_valence(tokens(df$text), afinn, normalize="dictionary")$sentiment,3)}
    
    if(lex == "lsd"){
    df$lsd<- round(textstat_polarity(tokens(df$text), lsd)$sentiment,3)}
    
    if(lex == "vader"){
    df$vader <- round(vader_df(df$text)$compound,3)}
  }
  
  # normalize sentiment scores if TRUE, VADER scores are already normalized
  if(normalize==TRUE){
    df$afinn <- round(normalize2(df$afinn), 3)
    df$lsd <- round(normalize2(df$lsd), 3)
  }
  
  # set sentiment scores to 0 if NA
  df[is.na(df)] <- 0
  
  return(df)
}

reviews_sentiment <- get_sentiment(reviews, c("afinn","lsd", "vader"))
twitter_sentiment <- get_sentiment(twitter, c("afinn","lsd", "vader"))
parlvote_sentiment <- get_sentiment(parlvote, c("afinn","lsd", "vader"))

# to save data frame as csv file
#write.csv(parlvote_sentiment, "datasets/parlvote_sentiment.csv", row.names = FALSE)

reviews_sentiment

3. Step: Calculate Statistics

Convert data into ternary format

Calculate Accuracy, Precision, Recall

reviews_afinn.stats
[[1]]
reviews_afinn.acc
[1] 0.53

4. Step: Plot Data

# plot columns
reviews_dfm <- melt(head(reviews_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

reviews_plot <- ggplot(reviews_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Reviews Sentiments")


twitter_dfm <- melt(head(twitter_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

twitter_plot <- ggplot(twitter_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge") +
                ggtitle("Twitter Sentiments")

parlvote_dfm <- melt(head(parlvote_sentiment,50)[,c('id','afinn','lsd','vader')],id.vars = 1)

parlvote_plot <- ggplot(parlvote_dfm,aes(x = id,y = value)) + 
                geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
                facet_wrap(~ variable, ncol = 1, scales="free_y")+
                ggtitle("ParlVote Sentiments")

#parlvote_norm_dfm <- melt(head(parlvote_sentiment,50)[,c('id','afinn_norm','lsd_norm','vader_norm')],id.vars = 1)

#parlvote_norm_plot <- ggplot(parlvote_norm_dfm,aes(x = id,y = value)) + 
              #  geom_bar(aes(fill = variable),stat = "identity",position = "dodge")+
               # facet_wrap(~ variable, ncol = 1, scales="free_y")+
                #ggtitle("ParlVote Sentiments")


reviews_plot

twitter_plot

parlvote_plot

#parlvote_norm_plot

Plot important words

# optional

Ranking of texts per lexicon

# sort per corpus and per tool
reviews_afinn.sort <- reviews_sentiment[order(reviews_sentiment$afinn, decreasing=TRUE),]

reviews_lsd.sort <- reviews_sentiment[order(reviews_sentiment$lsd, decreasing=TRUE),]

reviews_vader.sort <- reviews_sentiment[order(reviews_sentiment$vader, decreasing=TRUE),]


reviews_afinn.sort
reviews_lsd.sort
reviews_vader.sort

5. Step: Evaluation (Notes)

Comparison Groups: - compare binary (normalized) versions - each lexicon and data set - compare continuous (normalized) versions - each lexicon and data set - compare top n words per sentiment per tool? (= contribution to sentiment) - compare ranking (Stede’s idea): - rank texts per corpus and compare across tools -> is order similar? - tool’s performance - how is performance across domains? - is binary more accurate than continuous scoring? -

ADDITIONAL

Harry Potter - Dataset

# load harry potter dataset 
titles <- c("Philosopher's Stone", "Chamber of Secrets", "Prisoner of Azkaban",
            "Goblet of Fire", "Order of the Phoenix", "Half-Blood Prince",
            "Deathly Hallows")

books <- list(philosophers_stone, chamber_of_secrets, prisoner_of_azkaban,
           goblet_of_fire, order_of_the_phoenix, half_blood_prince,
           deathly_hallows)
  
series <- tibble()

for(i in seq_along(titles)) {
        
        clean <- tibble(chapter = seq_along(books[[i]]),
                        text = books[[i]]) %>%
             unnest_tokens(word, text) %>%
             mutate(book = titles[i]) %>%
             select(book, everything())

        series <- rbind(series, clean)
}

series$book <- factor(series$book, levels = rev(titles))

series
#book_groups <- series %>% group_by(book, chapter)
# tokenize hp1
#hp1_tokenized <- tokens_tolower(tokens(philosophers_stone, remove_punct = TRUE)) 
afinn_hp <- series %>%
       # group_by(book, chapter) %>% # add word for single word scores 
        #inner_join(get_sentiments("afinn")) %>%
        inner_join(textstat_valence(series$tokens,afinn)$sentiment) %>%
      #  group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
       # summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  #%>%
Warnung: Unknown or uninitialised column: `tokens`.
Fehler: textstat_valence() only works on character, corpus, dfm, tokens objects.

Harry Potter - AFINN Lexicon

afinn_hp2 <- series %>%
        group_by(book, chapter) %>% # add word for single word scores 
        inner_join(get_sentiments("afinn")) %>%
        group_by(book, chapter) %>% # add word for single word scores
        #summarise(sentiment = sum(value)) %>%
        summarise(sentiment = mean(value, na.rm = TRUE)) %>%
        mutate(method = "AFINN")  %>%
        ggplot(aes(chapter, sentiment, fill = book)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x")

afinn_hp2

#ggsave(plot = afinn, width = 15, height = 15, dpi = 300, filename = "afinn_hp_mean.png")

Lexicoder: HP

# select only the "negative" and "positive" categories
#data_dictionary_LSD2015_pos_neg <- data_dictionary_LSD2015[1:2]
#hp1_lsd <- tokens_lookup(hp1_tokenized, dictionary = data_dictionary_LSD2015_pos_neg)

polarity(data_dictionary_LSD2015) <- 
  list(pos = c("positive", "neg_negative"), neg = c("negative", "neg_positive"))

hp1_lsd <- textstat_polarity(hp1_tokenized, data_dictionary_LSD2015)

hp1_lsd_tokens <- tokens_lookup(hp1_tokenized, data_dictionary_LSD2015, nested_scope = "dictionary", exclusive = FALSE)
hp1_lsd.df <- as.data.frame.matrix(hp1_lsd)
hp1_lsd.df$chapter <- 1:nrow(hp1_lsd.df)

plot <- ggplot(hp1_lsd, aes(x =hp1_lsd.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - Lexicoder")

AFINN: HP

hp1_afinn <- textstat_valence(hp1_tokenized, afinn, normalize="dictionary")

hp1_afinn.df <- as.data.frame.matrix(hp1_afinn)
hp1_afinn.df$chapter <- 1:nrow(hp1_afinn.df)

plot <- ggplot(hp1_afinn.df, aes(x =hp1_afinn.df$chapter, y=sentiment)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-1.0, 1.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - AFINN")

VADER: HP

get_vader(philosophers_stone[1])

hp1_vader <- vader_df(philosophers_stone)
hp1_vader$chapter <- 1:nrow(hp1_vader)

plot <- ggplot(hp1_vader, aes(x =chapter, y=compound)) +
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE)
plot + ylim(-5.0, 5.0) + labs(y="sentiment", x = "chapter") + ggtitle("HP1 - VADER")

QUANTEDA.SENTIMENT

AFINN: HP

# Work with quanteda.sentiment on HP corpus:
# convert tibble to dataframe
series.df <- as.data.frame(series)

# tokenize books
series_tokenized <- series.df %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
series_tokenized$afinn <- textstat_valence(series_tokenized$tokens, afinn)$sentiment

# replace all 0 values with na
series_tokenized[series_tokenized == 0] <- NA

series_tokenized %>%
  group_by(book, chapter) %>% # group df by book and chapter to get sentiment per chapter
  summarise(sentiment = mean(afinn, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(chapter, sentiment, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("AFINN HP")

Lexicoder: HP

# Work with quanteda.sentiment on HP corpus:
# apply lexicoder lexicon
series$lsd <- textstat_polarity(tokens(series$text), data_dictionary_LSD2015)$sentiment 

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder HP")
plot 

Vader: HP

# apply vader lexicon to all HP books
series$vader <- vader_df(series$text)$compound

#series.df <- as.data.frame(series)

plot <- ggplot(series, aes(chapter, lsd, fill = book)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          facet_wrap(~ book, ncol = 2, scales = "free_x") +
          ggtitle("VADER HP")
plot 

REVIEWS DATASET

# load dataset
reviews <- readtext("datasets/goodreads_reviews_children_2.json", text_field = "review_text")

# convert to dataframe
reviews.df <- as.data.frame(reviews)

# add doc_id (i.e. according to index)
reviews.df$doc_id <- 1:nrow(reviews.df)

Sample Dataset

# get random sample of 50 reviews 
reviews_sample <- reviews.df[sample(1:nrow(reviews.df), 50,
   replace=FALSE),]

# get first 50 rows of data 
reviews_50 <- head(reviews.df,50)
reviews_50 = subset(reviews_50, select = c(doc_id,text,rating))

Get Translations of Dataset

# either via corpus 
reviews.corpus <- corpus(reviews)
docvars(reviews.corpus, "language") <- textcat(reviews.corpus)
reviews_en <- corpus_subset(reviews.corpus, language == "english", drop_docid = TRUE)

# or via dataframe logic
reviews.df$language <- textcat(reviews.df$text)

Sentiment Analysis on Reviews Dataset

AFINN

# Afinn
# tokenize 
reviews_tokenized <- reviews_50 %>%
  unnest_tokens(tokens, text)

# apply afinn lexicon
reviews_tokenized$afinn <- textstat_valence(reviews_tokenized$tokens, afinn)$sentiment

# replace all 0 values with na
reviews_tokenized[reviews_tokenized == 0] <- NA

# calculate mean scores for tokens per doc
afinn_scores <- reviews_tokenized %>%
  group_by(doc_id) %>% # group df by doc_id to get mean sentiment score
  summarise(total = mean(afinn, na.rm = TRUE)) #%>% # calculate mean w/o regarding na values

# add afinn scores to df 
reviews_50$afinn <- afinn_scores$total

# different version to get plot 
reviews_tokenized %>%
  group_by(doc_id) %>% # group df by book and chapter to get sentiment per chapter
  #reviews_tokenized$sentiment = mean(afinn, na.rm = TRUE) %>%
  summarise(sentiment = mean(afinn, na.rm = TRUE)) %>% # calculate mean w/o regarding na values
  mutate(method = "AFINN") %>% # add column with method 
        ggplot(aes(doc_id, sentiment, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("AFINN Reviews")

LEXICODER

# apply lexicoder lexicon
reviews_50$lsd <- textstat_polarity(tokens(reviews_50$text), data_dictionary_LSD2015)$sentiment 
#series.df <- as.data.frame(series)

plot <- ggplot(reviews_50, aes(doc_id, lsd, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Lexicoder Reviews")
plot 

Vader

reviews_50$vader <- vader_df(reviews_50$text)$compound

plot <- ggplot(reviews_50, aes(doc_id, vader, fill = doc_id)) + # plot sentiment of books
          geom_bar(alpha = 0.8, stat = "identity", show.legend = FALSE) +
          #facet_wrap(~ doc_id, ncol = 2, scales = "free_x") +
          ggtitle("Vader Reviews")
plot 
get_statistics <- function(df) {
  statistics <- data.frame(matrix(ncol=4, nrow=0))
  x <- c("accuracy", "precision", "recall", "F1")
  colnames(statistics) <- x
  lex1 <- "afinn_binary"
  lex2 <- "lsd_binary"
  lex3 <- "vader_binary"
  gold <- "rating_binary"
  
  lexicons <- c(lex1,lex2,lex3)
  
  for(lex in lexicons){
    confusion_matrix <- table(ACTUAL=df[[gold]], PREDICTED=df[[lex]])
    TN <- confusion_matrix[1]
    FN <- confusion_matrix[2]
    FP <- confusion_matrix[3]
    TP <- confusion_matrix[4]
  
    # calculate statistics
    precision <- TP/(TP+FP)
    accuracy <- (TP+TN)/(TP+TN+FP+FN)
    recall <- TP/(TP+FN)
    F1 <- (2*precision*recall)/(precision+recall)
  
    # add to table
    output <- c(accuracy,precision, recall, F1)
    statistics[lex,] = rbind(statistics[[lex]], output)
    }
    
  return(statistics)
  
}

get_statistics(test)

Test Functions

# complete function to get lexicon scores
get_sentiment <- function(data, lexicons){
  
  for(lex in lexicons){
    if(lex == "afinn"){
      
    data$afinn <- textstat_valence(tokens(data$text), afinn, normalize="dictionary")$sentiment
    }
    
    if(lex == "lsd"){
    data$lsd<- textstat_polarity(tokens(data$text), data_dictionary_LSD2015)$sentiment
    }
    
    if(lex == "vader"){
    data$vader <- vader_df(data$text)$compound
    }
  }
  data[is.na(data)] <- 0
  return(data)
}

reviews_sentiment <- get_sentiment(reviews, c("afinn","lsd", "vader"))
twitter_sentiment <- get_sentiment(twitter, c("afinn","lsd", "vader"))
parlvote_sentiment <- get_sentiment(parlvote, c("afinn","lsd", "vader"))

parlvote_sentiment
#write.csv(parlvote_sentiment, "datasets/parlvote_sentiment.csv", row.names = FALSE)

```

LS0tCnRpdGxlOiAiQ29tcGFyaXNvbiBvZiBTZW50aW1lbnQgVG9vbHMgYWNyb3NzIERvbWFpbnMiCm91dHB1dDogaHRtbF9ub3RlYm9vawotLS0KYGBge3J9CiMgbG9hZCByZXF1aXJlZCBsaWJyYXJpZXMKCiMgdG8gdXNlIGhhcnJ5IHBvdHRlciBkYXRhc2V0CiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJicmFkbGV5Ym9laG1rZS9oYXJyeXBvdHRlciIpCiMgZGV2dG9vbHM6Omluc3RhbGxfZ2l0aHViKCJxdWFudGVkYS9xdWFudGVkYS5zZW50aW1lbnQiKQojIGRldnRvb2xzOjppbnN0YWxsX2dpdGh1YigicXVhbnRlZGEvcXVhbnRlZGEuY29ycG9yYSIpCgpsaWJyYXJ5KHF1YW50ZWRhKQpsaWJyYXJ5KHJlYWR0ZXh0KQpsaWJyYXJ5KGNvcnB1cykKbGlicmFyeSh0aWR5dmVyc2UpCmxpYnJhcnkoc3RyaW5ncikKbGlicmFyeSh0aWR5dGV4dCkKbGlicmFyeShoYXJyeXBvdHRlcikKbGlicmFyeShkcGx5cikKbGlicmFyeShxdWFudGVkYS5zZW50aW1lbnQpCmxpYnJhcnkodmFkZXIpCmxpYnJhcnkoY2FyZXQpCmxpYnJhcnkocmVzaGFwZTIpCgoKcmVxdWlyZShxdWFudGVkYSkKcmVxdWlyZShxdWFudGVkYS5jb3Jwb3JhKQpyZXF1aXJlKHF1YW50ZWRhLnNlbnRpbWVudCkKI2xpYnJhcnkoInF1YW50ZWRhIiwgd2Fybi5jb25mbGljdHMgPSBGQUxTRSwgcXVpZXRseSA9IFRSVUUpCmBgYAoKIyAxLiBTdGVwOiBMb2FkIERhdGEgJiBMZXhpY29ucwpgYGB7cn0KIyBsb2FkIGRhdGFzZXRzCnJldmlld3MgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfcmV2aWV3LnJkcyIpCnR3aXR0ZXIgPC0gcmVhZFJEUyhmaWxlPSJkYXRhc2V0cy9yZWRfdHdpdHRlci5yZHMiKQpwYXJsdm90ZSA8LSByZWFkUkRTKGZpbGU9ImRhdGFzZXRzL3JlZF9wYXJsX3ZvdGUucmRzIikKCiMgbG9hZCBsZXhpY29ucwphZmlubiA8LSBkYXRhX2RpY3Rpb25hcnlfQUZJTk4KbHNkIDwtIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1CmBgYAoKYGBge3J9CnJldmlld3MKdHdpdHRlcgpwYXJsdm90ZQpgYGAKIyAyLiBTdGVwOiBQZXJmb3JtIFNlbnRpbWVudCBBbmFseXNpcwojIyMgTm9ybWFsaXplIFNjb3JlcwpgYGB7cn0KIyBEQVRBIE5PUk1BTElaQVRJT04KIyBOb3JtYWxpemUgZGF0YSB2aWEgbWluaW11bi9tYXhpbXVtIG5vcm1hbGl6YXRpb24sIGVpdGhlciBieSBzY2FsaW5nIHZhbHVlcyBmcm9tIDAgdG8gMSBvciAtMSB0byAxCiMgCiMgQXJnOgojICAgeDogaW5wdXQgdmFsdWVzIChlLmcuIGNvbHVtbiBvZiBkYXRhIGZyYW1lKQojICAgCiMgUmV0dXJuczogCiMgICBub3JtYWxpemVkIGRhdGEKCgojIG1pbi9tYXggbm9ybWFsaXphdGlvbiBmcm9tIDAgdG8gMQpub3JtYWxpemUgPC0gZnVuY3Rpb24oeCwgbmEucm0gPSBUUlVFKXsKICByZXR1cm4oKHgtbWluKHgpKSAvIChtYXgoeCktbWluKHgpKSl9CgojIG1pbi9tYXggbm9ybWFsaXphdGlvbiBmcm9tIC0xIHRvIDEKbm9ybWFsaXplMiA8LSBmdW5jdGlvbih4LCBuYS5ybSA9IFRSVUUpewogIHJldHVybigyKiAoKHggLSBtaW4oeCkpIC8gKG1heCh4KS1taW4oeCkpKS0xKX0KYGBgCgojIyMgQ2FsY3VsYXRlIFNlbnRpbWVudCBTY29yZXMKYGBge3J9CiMgQ2FsY3VsYXRlIHNlbnRpbWVudCBzY29yZXMgZm9yIGRpZmZlcmVudCBsZXhpY29ucyBhbmQgaW5wdXQgZGF0YSBmcmFtZXMKIyAKIyBBcmc6CiMgICBkYXRhOiBpbnB1dCBkYXRhIGZyYW1lCiMgICBsZXhpY29uczogbmFtZXMgb2YgbGV4aWNvbnMgdGhhdCBzaG91bGQgYmUgdXNlZCBmb3Igc2VudGltZW50IHNjb3JpbmcKIyAgIG5vcm1hbGl6ZTogVFJVRSBvciBGQUxTRSwgdG8gb3B0aW9uYWxseSBub3JtYWxpemUgc2VudGltZW50IHNjb3JlcwojICAgCiMgUmV0dXJuczogCiMgICBEYXRhIGZyYW1lIHdpdGggKG5vcm1hbGl6ZWQpIHNlbnRpbWVudCBzb3JlcyBmb3IgY2hvc2VuIGxleGljb25zCgpnZXRfc2VudGltZW50IDwtIGZ1bmN0aW9uKGRmLCBsZXhpY29ucywgbm9ybWFsaXplID0gVFJVRSl7CiAgCiAgIyBmb3IgZWFjaCBsZXhpY29uLCBnZXQgc2VudGltZW50IHNjb3JlcyBhbmQgc2F2ZSBzY29yZXMgaW4gbmV3IGNvbHVtbiBvZiBkYXRhIGZyYW1lCiAgZm9yKGxleCBpbiBsZXhpY29ucyl7CiAgICAKICAgIGlmKGxleCA9PSAiYWZpbm4iKXsKICAgIGRmJGFmaW5uIDwtIHJvdW5kKHRleHRzdGF0X3ZhbGVuY2UodG9rZW5zKGRmJHRleHQpLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5Iikkc2VudGltZW50LDMpfQogICAgCiAgICBpZihsZXggPT0gImxzZCIpewogICAgZGYkbHNkPC0gcm91bmQodGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKGRmJHRleHQpLCBsc2QpJHNlbnRpbWVudCwzKX0KICAgIAogICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgZGYkdmFkZXIgPC0gcm91bmQodmFkZXJfZGYoZGYkdGV4dCkkY29tcG91bmQsMyl9CiAgfQogIAogICMgbm9ybWFsaXplIHNlbnRpbWVudCBzY29yZXMgaWYgVFJVRSwgVkFERVIgc2NvcmVzIGFyZSBhbHJlYWR5IG5vcm1hbGl6ZWQKICBpZihub3JtYWxpemU9PVRSVUUpewogICAgZGYkYWZpbm4gPC0gcm91bmQobm9ybWFsaXplMihkZiRhZmlubiksIDMpCiAgICBkZiRsc2QgPC0gcm91bmQobm9ybWFsaXplMihkZiRsc2QpLCAzKQogIH0KICAKICAjIHNldCBzZW50aW1lbnQgc2NvcmVzIHRvIDAgaWYgTkEKICBkZltpcy5uYShkZildIDwtIDAKICAKICByZXR1cm4oZGYpCn0KCnJldmlld3Nfc2VudGltZW50IDwtIGdldF9zZW50aW1lbnQocmV2aWV3cywgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSkKdHdpdHRlcl9zZW50aW1lbnQgPC0gZ2V0X3NlbnRpbWVudCh0d2l0dGVyLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQpwYXJsdm90ZV9zZW50aW1lbnQgPC0gZ2V0X3NlbnRpbWVudChwYXJsdm90ZSwgYygiYWZpbm4iLCJsc2QiLCAidmFkZXIiKSkKCiMgdG8gc2F2ZSBkYXRhIGZyYW1lIGFzIGNzdiBmaWxlCiN3cml0ZS5jc3YocGFybHZvdGVfc2VudGltZW50LCAiZGF0YXNldHMvcGFybHZvdGVfc2VudGltZW50LmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQoKcmV2aWV3c19zZW50aW1lbnQKYGBgCgojIDMuIFN0ZXA6IENhbGN1bGF0ZSBTdGF0aXN0aWNzCiMjIyBDb252ZXJ0IGRhdGEgaW50byB0ZXJuYXJ5IGZvcm1hdApgYGB7cn0KIyBDb252ZXJ0IGZpbmFsIHZhbHVlcyBpbnRvIHRlcm5hcnkgKDEgPSBwb3NpdGl2ZSwgMCA9IG5ldXRyYWwsIC0xID0gbmVnYXRpdmUpIGZvcm1hdCBmb3IgZXZhbHVhdGlvbiBhbmQgY29tcGFyaXNvbgojIAojIEFyZzoKIyAgIGRmOiBpbnB1dCBkYXRhIGZyYW1lIHRoYXQgY29udGFpbnMgdGhlIGNvbHVtbnMgdG8gYmUgY29udmVydGVkIGludG8gdGVybmFyeSBmb3JtYXQKIyAgIHRvX2NoYW5nZTogY29sdW1uIG5hbWVzIHRoYXQgc2hvdWxkIGJlIGNvbnZlcnRlZCBpbnRvIHRlcm5hcnkgZm9ybWF0CiMgICBnb2xkOiBnb2xkc3RhbmRhcmQsIGkuZS4gcmF0aW5nLCB3aGljaCBoYXMgdG8gYmUgY2hhbmdlZCB0byB0ZXJuYXJ5IGZvcm1hdAojICAgCiMgUmV0dXJuczogCiMgICBkYXRhIGZyYW1lIHdpdGggY29udmVydGVkIHZhbHVlcwoKZ2V0X2JpbmFyeSA8LSBmdW5jdGlvbihkZiwgdG9fY2hhbmdlLCBnb2xkKXsKICBkZiAlPiUgCiAgICBtdXRhdGVfYXQodG9fY2hhbmdlLCBmdW5jdGlvbih4KXsKICAgICAgIyBtdXRhdGUgdmFsdWVzIGdyZWF0ZXIgdGhhbiAwIHRvIDEgKHBvc2l0aXZlKSwgZXF1YWwgdG8gMCB0byAwIChuZXV0cmFsKSBhbmQgc21hbGxlciB0aGFuIDAgdG8gLTEgKG5lZ2F0aXZlKQogICAgICBjYXNlX3doZW4oeCA+IDAgfiAxLCB4IDwgMCB+IC0xLCB4ID09IDAgfiAwKX0pICU+JSAKICAgIAogICAgbXV0YXRlX2F0KGdvbGQsIGZ1bmN0aW9uKHgpewogICAgICAjIGlmIHggaXMgYmV0d2VlbiAwLTUsIG11dGF0ZSB2YWx1ZXMgZ3JlYXRlciB0aGFuIDAgdG8gMSwgZXF1YWwgdG8gMCB0byAwIGFuZCBzbWFsbGVyIHRoYW4gMCB0byAtMQogICAgICBjYXNlX3doZW4oYmV0d2Vlbih4LDAsNSkgJiB4ID4gMyB+IDEsIGJldHdlZW4oeCwwLDUpICYgeCA9PSAzIH4gMCwgYmV0d2Vlbih4LDAsNSkgJiB4IDwgMyB+IC0xLAogICAgICAgIHggPT0gIlBvc2l0aXZlIiB+IDEsIHggPT0gIk5lZ2F0aXZlIiB+IC0xLCB4ID09ICJOZXV0cmFsIiB+IDApfSkKfQoKcmV2aWV3c19iaW5hcnkgPC0gZ2V0X2JpbmFyeShyZXZpZXdzX3NlbnRpbWVudCwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpLCAicmF0aW5nIikKdHdpdHRlcl9iaW5hcnkgPC0gZ2V0X2JpbmFyeSh0d2l0dGVyX3NlbnRpbWVudCwgYygiYWZpbm4iLCJ2YWRlciIsImxzZCIpLCAicmF0aW5nIikKcGFybHZvdGVfYmluYXJ5IDwtIGdldF9iaW5hcnkocGFybHZvdGVfc2VudGltZW50LCBjKCJhZmlubiIsInZhZGVyIiwibHNkIiksICJyYXRpbmciKQoKcGFybHZvdGVfYmluYXJ5CgpgYGAKIyMjIENhbGN1bGF0ZSBBY2N1cmFjeSwgUHJlY2lzaW9uLCBSZWNhbGwKYGBge3J9CiMgRnVuY3Rpb24gdG8gZ2V0IHN0YXRpc3RpY3MgKGFjY3VyYWN5LCBwcmVjaXNpb24sIHJlY2FsbCkgb2YgZGF0YSBmcmFtZSBmb3Igc3BlY2lmaWMgbGV4aWNvbgojCiMgQXJnOiAKIyAgIGRmOiBkYXRhIGZyYW1lIHRoYXQgd2Ugd2FudCBzdGF0aXN0aWNzIG9mCiMgICBsZXhpY29uOiBiaW5hcnkvdGVybmFyeSBsZXhpY29uIHRoYXQgc2hvdWxkIGJlIGV2YWx1YXRlZAojCiMgUmV0dXJuczoKIyAgIG92ZXJhbGwgYWNjdXJhY3kgc2NvcmUKIyAgIGRhdGEgZnJhbWUgd2l0aCBzdGF0aXN0aWNzCiAgCmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmLCBsZXhpY29uKXsKICAKICAjIGNyZWF0ZSBjb25mdXNpb24gbWF0cml4ICh2aWEgY2FyZXQgbGlicmFyeSkgdG8gZ2V0IHN0YXRpc3RpY3Mgb2YgZGF0YSBmcmFtZQogIGNtIDwtIGNvbmZ1c2lvbk1hdHJpeChmYWN0b3IoZGZbW2xleGljb25dXSksIGZhY3RvcihkZiRyYXRpbmcpLCBtb2RlPSJwcmVjX3JlY2FsbCIpCiAgCiAgYWNjIDwtIChhcy5kYXRhLmZyYW1lKGNtJG92ZXJhbGwpKVsiQWNjdXJhY3kiLF0KICBzdGF0cyA8LSBhcy5kYXRhLmZyYW1lKGNtJGJ5Q2xhc3MpCiAgcmVzdWx0cyA8LSBsaXN0KGFjYywgc3RhdHMpCiAgCiAgIyBjaGVjayBob3cgdG8gcmV0dXJuIHR3byBzdGF0ZW1lbnRzCiAgcmV0dXJuKHJlc3VsdHMpCn0KCnJldmlld3NfYWZpbm4uYWNjIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAiYWZpbm4iKVtbMV1dWzFdCnJldmlld3NfYWZpbm4uc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJhZmlubiIpWzJdCgpyZXZpZXdzX2xzZC5hY2MgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJsc2QiKVtbMV1dWzFdCnJldmlld3NfbHNkLnN0YXRzIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAibHNkIilbMl0KCnJldmlld3NfdmFkZXIuYWNjIDwtIGdldF9zdGF0aXN0aWNzKHJldmlld3NfYmluYXJ5LCAidmFkZXIiKVtbMV1dWzFdCnJldmlld3NfdmFkZXIuc3RhdHMgPC0gZ2V0X3N0YXRpc3RpY3MocmV2aWV3c19iaW5hcnksICJ2YWRlciIpWzJdCgpyZXZpZXdzX2FmaW5uLnN0YXRzCnJldmlld3NfYWZpbm4uYWNjCmBgYAoKIyA0LiBTdGVwOiBQbG90IERhdGEgCmBgYHtyfQojIHBsb3QgY29sdW1ucwpyZXZpZXdzX2RmbSA8LSBtZWx0KGhlYWQocmV2aWV3c19zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgpyZXZpZXdzX3Bsb3QgPC0gZ2dwbG90KHJldmlld3NfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUmV2aWV3cyBTZW50aW1lbnRzIikKCgp0d2l0dGVyX2RmbSA8LSBtZWx0KGhlYWQodHdpdHRlcl9zZW50aW1lbnQsNTApWyxjKCdpZCcsJ2FmaW5uJywnbHNkJywndmFkZXInKV0saWQudmFycyA9IDEpCgp0d2l0dGVyX3Bsb3QgPC0gZ2dwbG90KHR3aXR0ZXJfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikgKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiVHdpdHRlciBTZW50aW1lbnRzIikKCnBhcmx2b3RlX2RmbSA8LSBtZWx0KGhlYWQocGFybHZvdGVfc2VudGltZW50LDUwKVssYygnaWQnLCdhZmlubicsJ2xzZCcsJ3ZhZGVyJyldLGlkLnZhcnMgPSAxKQoKcGFybHZvdGVfcGxvdCA8LSBnZ3Bsb3QocGFybHZvdGVfZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAgIGdlb21fYmFyKGFlcyhmaWxsID0gdmFyaWFibGUpLHN0YXQgPSAiaWRlbnRpdHkiLHBvc2l0aW9uID0gImRvZGdlIikrCiAgICAgICAgICAgICAgICBmYWNldF93cmFwKH4gdmFyaWFibGUsIG5jb2wgPSAxLCBzY2FsZXM9ImZyZWVfeSIpKwogICAgICAgICAgICAgICAgZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgojcGFybHZvdGVfbm9ybV9kZm0gPC0gbWVsdChoZWFkKHBhcmx2b3RlX3NlbnRpbWVudCw1MClbLGMoJ2lkJywnYWZpbm5fbm9ybScsJ2xzZF9ub3JtJywndmFkZXJfbm9ybScpXSxpZC52YXJzID0gMSkKCiNwYXJsdm90ZV9ub3JtX3Bsb3QgPC0gZ2dwbG90KHBhcmx2b3RlX25vcm1fZGZtLGFlcyh4ID0gaWQseSA9IHZhbHVlKSkgKyAKICAgICAgICAgICAgICAjICBnZW9tX2JhcihhZXMoZmlsbCA9IHZhcmlhYmxlKSxzdGF0ID0gImlkZW50aXR5Iixwb3NpdGlvbiA9ICJkb2RnZSIpKwogICAgICAgICAgICAgICAjIGZhY2V0X3dyYXAofiB2YXJpYWJsZSwgbmNvbCA9IDEsIHNjYWxlcz0iZnJlZV95IikrCiAgICAgICAgICAgICAgICAjZ2d0aXRsZSgiUGFybFZvdGUgU2VudGltZW50cyIpCgoKcmV2aWV3c19wbG90CnR3aXR0ZXJfcGxvdApwYXJsdm90ZV9wbG90CiNwYXJsdm90ZV9ub3JtX3Bsb3QKYGBgCiMjIyBQbG90IGltcG9ydGFudCB3b3JkcwpgYGB7cn0KIyBvcHRpb25hbApgYGAKCiMjIyBSYW5raW5nIG9mIHRleHRzIHBlciBsZXhpY29uCmBgYHtyfQojIHNvcnQgcGVyIGNvcnB1cyBhbmQgcGVyIHRvb2wKcmV2aWV3c19hZmlubi5zb3J0IDwtIHJldmlld3Nfc2VudGltZW50W29yZGVyKHJldmlld3Nfc2VudGltZW50JGFmaW5uLCBkZWNyZWFzaW5nPVRSVUUpLF0KCnJldmlld3NfbHNkLnNvcnQgPC0gcmV2aWV3c19zZW50aW1lbnRbb3JkZXIocmV2aWV3c19zZW50aW1lbnQkbHNkLCBkZWNyZWFzaW5nPVRSVUUpLF0KCnJldmlld3NfdmFkZXIuc29ydCA8LSByZXZpZXdzX3NlbnRpbWVudFtvcmRlcihyZXZpZXdzX3NlbnRpbWVudCR2YWRlciwgZGVjcmVhc2luZz1UUlVFKSxdCgoKcmV2aWV3c19hZmlubi5zb3J0CnJldmlld3NfbHNkLnNvcnQKcmV2aWV3c192YWRlci5zb3J0CmBgYAoKIyA1LiBTdGVwOiBFdmFsdWF0aW9uIChOb3RlcykKLSBldmFsdWF0ZSBkYXRhLyBjb21wYXJlIGRhdGEKLSBhY2N1cmFjeSwgcHJlY2lzaW9uLCByZWNhbGwgCi0gcGVhcnNvbnMgY29lZmZpY2llbnQgCgpDb21wYXJpc29uIEdyb3VwczoKLSBjb21wYXJlIGJpbmFyeSAobm9ybWFsaXplZCkgdmVyc2lvbnMKICAtIGVhY2ggbGV4aWNvbiBhbmQgZGF0YSBzZXQKLSBjb21wYXJlIGNvbnRpbnVvdXMgKG5vcm1hbGl6ZWQpIHZlcnNpb25zCiAgLSBlYWNoIGxleGljb24gYW5kIGRhdGEgc2V0Ci0gY29tcGFyZSB0b3AgbiB3b3JkcyBwZXIgc2VudGltZW50IHBlciB0b29sPyAoPSBjb250cmlidXRpb24gdG8gc2VudGltZW50KQotIGNvbXBhcmUgcmFua2luZyAoU3RlZGUncyBpZGVhKToKICAtIHJhbmsgdGV4dHMgcGVyIGNvcnB1cyBhbmQgY29tcGFyZSBhY3Jvc3MgdG9vbHMgLT4gaXMgb3JkZXIgc2ltaWxhcj8KLSB0b29sJ3MgcGVyZm9ybWFuY2UKICAtIGhvdyBpcyBwZXJmb3JtYW5jZSBhY3Jvc3MgZG9tYWlucz8KICAtIGlzIGJpbmFyeSBtb3JlIGFjY3VyYXRlIHRoYW4gY29udGludW91cyBzY29yaW5nPwogIC0gCgoKCgoKCgoKCiMjIyBBRERJVElPTkFMCiMgSGFycnkgUG90dGVyIC0gRGF0YXNldApgYGB7cn0KIyBsb2FkIGhhcnJ5IHBvdHRlciBkYXRhc2V0IAp0aXRsZXMgPC0gYygiUGhpbG9zb3BoZXIncyBTdG9uZSIsICJDaGFtYmVyIG9mIFNlY3JldHMiLCAiUHJpc29uZXIgb2YgQXprYWJhbiIsCiAgICAgICAgICAgICJHb2JsZXQgb2YgRmlyZSIsICJPcmRlciBvZiB0aGUgUGhvZW5peCIsICJIYWxmLUJsb29kIFByaW5jZSIsCiAgICAgICAgICAgICJEZWF0aGx5IEhhbGxvd3MiKQoKYm9va3MgPC0gbGlzdChwaGlsb3NvcGhlcnNfc3RvbmUsIGNoYW1iZXJfb2Zfc2VjcmV0cywgcHJpc29uZXJfb2ZfYXprYWJhbiwKICAgICAgICAgICBnb2JsZXRfb2ZfZmlyZSwgb3JkZXJfb2ZfdGhlX3Bob2VuaXgsIGhhbGZfYmxvb2RfcHJpbmNlLAogICAgICAgICAgIGRlYXRobHlfaGFsbG93cykKICAKc2VyaWVzIDwtIHRpYmJsZSgpCgpmb3IoaSBpbiBzZXFfYWxvbmcodGl0bGVzKSkgewogICAgICAgIAogICAgICAgIGNsZWFuIDwtIHRpYmJsZShjaGFwdGVyID0gc2VxX2Fsb25nKGJvb2tzW1tpXV0pLAogICAgICAgICAgICAgICAgICAgICAgICB0ZXh0ID0gYm9va3NbW2ldXSkgJT4lCiAgICAgICAgICAgICB1bm5lc3RfdG9rZW5zKHdvcmQsIHRleHQpICU+JQogICAgICAgICAgICAgbXV0YXRlKGJvb2sgPSB0aXRsZXNbaV0pICU+JQogICAgICAgICAgICAgc2VsZWN0KGJvb2ssIGV2ZXJ5dGhpbmcoKSkKCiAgICAgICAgc2VyaWVzIDwtIHJiaW5kKHNlcmllcywgY2xlYW4pCn0KCnNlcmllcyRib29rIDwtIGZhY3RvcihzZXJpZXMkYm9vaywgbGV2ZWxzID0gcmV2KHRpdGxlcykpCgpzZXJpZXMKI2Jvb2tfZ3JvdXBzIDwtIHNlcmllcyAlPiUgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikKIyB0b2tlbml6ZSBocDEKI2hwMV90b2tlbml6ZWQgPC0gdG9rZW5zX3RvbG93ZXIodG9rZW5zKHBoaWxvc29waGVyc19zdG9uZSwgcmVtb3ZlX3B1bmN0ID0gVFJVRSkpIApgYGAKYGBge3J9CmFmaW5uX2hwIDwtIHNlcmllcyAlPiUKICAgICAgICMgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcyAKICAgICAgICAjaW5uZXJfam9pbihnZXRfc2VudGltZW50cygiYWZpbm4iKSkgJT4lCiAgICAgICAgaW5uZXJfam9pbih0ZXh0c3RhdF92YWxlbmNlKHNlcmllcyR0b2tlbnMsYWZpbm4pJHNlbnRpbWVudCkgJT4lCiAgICAgICMgIGdyb3VwX2J5KGJvb2ssIGNoYXB0ZXIpICU+JSAjIGFkZCB3b3JkIGZvciBzaW5nbGUgd29yZCBzY29yZXMKICAgICAgICAjc3VtbWFyaXNlKHNlbnRpbWVudCA9IHN1bSh2YWx1ZSkpICU+JQogICAgICAgIyBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICAjJT4lCiAgICAgICAjIGdncGxvdChhZXMoY2hhcHRlciwgc2VudGltZW50LCBmaWxsID0gYm9vaykpICsKICAgICAgICMgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICMgICBmYWNldF93cmFwKH4gYm9vaywgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKQoKYWZpbm5faHAKYGBgCgoKIyMjIEhhcnJ5IFBvdHRlciAtIEFGSU5OIExleGljb24KYGBge3J9CmFmaW5uX2hwMiA8LSBzZXJpZXMgJT4lCiAgICAgICAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgYWRkIHdvcmQgZm9yIHNpbmdsZSB3b3JkIHNjb3JlcyAKICAgICAgICBpbm5lcl9qb2luKGdldF9zZW50aW1lbnRzKCJhZmlubiIpKSAlPiUKICAgICAgICBncm91cF9ieShib29rLCBjaGFwdGVyKSAlPiUgIyBhZGQgd29yZCBmb3Igc2luZ2xlIHdvcmQgc2NvcmVzCiAgICAgICAgI3N1bW1hcmlzZShzZW50aW1lbnQgPSBzdW0odmFsdWUpKSAlPiUKICAgICAgICBzdW1tYXJpc2Uoc2VudGltZW50ID0gbWVhbih2YWx1ZSwgbmEucm0gPSBUUlVFKSkgJT4lCiAgICAgICAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICAlPiUKICAgICAgICBnZ3Bsb3QoYWVzKGNoYXB0ZXIsIHNlbnRpbWVudCwgZmlsbCA9IGJvb2spKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpCgphZmlubl9ocDIKCiNnZ3NhdmUocGxvdCA9IGFmaW5uLCB3aWR0aCA9IDE1LCBoZWlnaHQgPSAxNSwgZHBpID0gMzAwLCBmaWxlbmFtZSA9ICJhZmlubl9ocF9tZWFuLnBuZyIpCmBgYAoKIyBMZXhpY29kZXI6IEhQCmBgYHtyfQojIHNlbGVjdCBvbmx5IHRoZSAibmVnYXRpdmUiIGFuZCAicG9zaXRpdmUiIGNhdGVnb3JpZXMKI2RhdGFfZGljdGlvbmFyeV9MU0QyMDE1X3Bvc19uZWcgPC0gZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTVbMToyXQojaHAxX2xzZCA8LSB0b2tlbnNfbG9va3VwKGhwMV90b2tlbml6ZWQsIGRpY3Rpb25hcnkgPSBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNV9wb3NfbmVnKQoKcG9sYXJpdHkoZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpIDwtIAogIGxpc3QocG9zID0gYygicG9zaXRpdmUiLCAibmVnX25lZ2F0aXZlIiksIG5lZyA9IGMoIm5lZ2F0aXZlIiwgIm5lZ19wb3NpdGl2ZSIpKQoKaHAxX2xzZCA8LSB0ZXh0c3RhdF9wb2xhcml0eShocDFfdG9rZW5pemVkLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkKCmhwMV9sc2RfdG9rZW5zIDwtIHRva2Vuc19sb29rdXAoaHAxX3Rva2VuaXplZCwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUsIG5lc3RlZF9zY29wZSA9ICJkaWN0aW9uYXJ5IiwgZXhjbHVzaXZlID0gRkFMU0UpCmhwMV9sc2QuZGYgPC0gYXMuZGF0YS5mcmFtZS5tYXRyaXgoaHAxX2xzZCkKaHAxX2xzZC5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfbHNkLmRmKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX2xzZCwgYWVzKHggPWhwMV9sc2QuZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gTGV4aWNvZGVyIikKYGBgCiMgQUZJTk46IEhQCmBgYHtyfQpocDFfYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShocDFfdG9rZW5pemVkLCBhZmlubiwgbm9ybWFsaXplPSJkaWN0aW9uYXJ5IikKCmhwMV9hZmlubi5kZiA8LSBhcy5kYXRhLmZyYW1lLm1hdHJpeChocDFfYWZpbm4pCmhwMV9hZmlubi5kZiRjaGFwdGVyIDwtIDE6bnJvdyhocDFfYWZpbm4uZGYpCgpwbG90IDwtIGdncGxvdChocDFfYWZpbm4uZGYsIGFlcyh4ID1ocDFfYWZpbm4uZGYkY2hhcHRlciwgeT1zZW50aW1lbnQpKSArCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpCnBsb3QgKyB5bGltKC0xLjAsIDEuMCkgKyBsYWJzKHk9InNlbnRpbWVudCIsIHggPSAiY2hhcHRlciIpICsgZ2d0aXRsZSgiSFAxIC0gQUZJTk4iKQpgYGAKIyBWQURFUjogSFAKYGBge3J9CmdldF92YWRlcihwaGlsb3NvcGhlcnNfc3RvbmVbMV0pCgpocDFfdmFkZXIgPC0gdmFkZXJfZGYocGhpbG9zb3BoZXJzX3N0b25lKQpocDFfdmFkZXIkY2hhcHRlciA8LSAxOm5yb3coaHAxX3ZhZGVyKQoKcGxvdCA8LSBnZ3Bsb3QoaHAxX3ZhZGVyLCBhZXMoeCA9Y2hhcHRlciwgeT1jb21wb3VuZCkpICsKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkKcGxvdCArIHlsaW0oLTUuMCwgNS4wKSArIGxhYnMoeT0ic2VudGltZW50IiwgeCA9ICJjaGFwdGVyIikgKyBnZ3RpdGxlKCJIUDEgLSBWQURFUiIpCmBgYAojIFFVQU5URURBLlNFTlRJTUVOVAojIEFGSU5OOiBIUApgYGB7cn0KIyBXb3JrIHdpdGggcXVhbnRlZGEuc2VudGltZW50IG9uIEhQIGNvcnB1czoKIyBjb252ZXJ0IHRpYmJsZSB0byBkYXRhZnJhbWUKc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKIyB0b2tlbml6ZSBib29rcwpzZXJpZXNfdG9rZW5pemVkIDwtIHNlcmllcy5kZiAlPiUKICB1bm5lc3RfdG9rZW5zKHRva2VucywgdGV4dCkKCiMgYXBwbHkgYWZpbm4gbGV4aWNvbgpzZXJpZXNfdG9rZW5pemVkJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2Uoc2VyaWVzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpzZXJpZXNfdG9rZW5pemVkW3Nlcmllc190b2tlbml6ZWQgPT0gMF0gPC0gTkEKCnNlcmllc190b2tlbml6ZWQgJT4lCiAgZ3JvdXBfYnkoYm9vaywgY2hhcHRlcikgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhjaGFwdGVyLCBzZW50aW1lbnQsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkFGSU5OIEhQIikKYGBgCiMgTGV4aWNvZGVyOiBIUCAgCmBgYHtyfQojIFdvcmsgd2l0aCBxdWFudGVkYS5zZW50aW1lbnQgb24gSFAgY29ycHVzOgojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnNlcmllcyRsc2QgPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKHNlcmllcyR0ZXh0KSwgZGF0YV9kaWN0aW9uYXJ5X0xTRDIwMTUpJHNlbnRpbWVudCAKCiNzZXJpZXMuZGYgPC0gYXMuZGF0YS5mcmFtZShzZXJpZXMpCgpwbG90IDwtIGdncGxvdChzZXJpZXMsIGFlcyhjaGFwdGVyLCBsc2QsIGZpbGwgPSBib29rKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgIGZhY2V0X3dyYXAofiBib29rLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBIUCIpCnBsb3QgCmBgYAoKIyBWYWRlcjogSFAKYGBge3J9CiMgYXBwbHkgdmFkZXIgbGV4aWNvbiB0byBhbGwgSFAgYm9va3MKc2VyaWVzJHZhZGVyIDwtIHZhZGVyX2RmKHNlcmllcyR0ZXh0KSRjb21wb3VuZAoKI3Nlcmllcy5kZiA8LSBhcy5kYXRhLmZyYW1lKHNlcmllcykKCnBsb3QgPC0gZ2dwbG90KHNlcmllcywgYWVzKGNoYXB0ZXIsIGxzZCwgZmlsbCA9IGJvb2spKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgZmFjZXRfd3JhcCh+IGJvb2ssIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVkFERVIgSFAiKQpwbG90IApgYGAKCiMgUkVWSUVXUyBEQVRBU0VUCmBgYHtyfQojIGxvYWQgZGF0YXNldApyZXZpZXdzIDwtIHJlYWR0ZXh0KCJkYXRhc2V0cy9nb29kcmVhZHNfcmV2aWV3c19jaGlsZHJlbl8yLmpzb24iLCB0ZXh0X2ZpZWxkID0gInJldmlld190ZXh0IikKCiMgY29udmVydCB0byBkYXRhZnJhbWUKcmV2aWV3cy5kZiA8LSBhcy5kYXRhLmZyYW1lKHJldmlld3MpCgojIGFkZCBkb2NfaWQgKGkuZS4gYWNjb3JkaW5nIHRvIGluZGV4KQpyZXZpZXdzLmRmJGRvY19pZCA8LSAxOm5yb3cocmV2aWV3cy5kZikKYGBgCgojIyMgU2FtcGxlIERhdGFzZXQKYGBge3J9CiMgZ2V0IHJhbmRvbSBzYW1wbGUgb2YgNTAgcmV2aWV3cyAKcmV2aWV3c19zYW1wbGUgPC0gcmV2aWV3cy5kZltzYW1wbGUoMTpucm93KHJldmlld3MuZGYpLCA1MCwKICAgcmVwbGFjZT1GQUxTRSksXQoKIyBnZXQgZmlyc3QgNTAgcm93cyBvZiBkYXRhIApyZXZpZXdzXzUwIDwtIGhlYWQocmV2aWV3cy5kZiw1MCkKcmV2aWV3c181MCA9IHN1YnNldChyZXZpZXdzXzUwLCBzZWxlY3QgPSBjKGRvY19pZCx0ZXh0LHJhdGluZykpCmBgYAojIyMgR2V0IFRyYW5zbGF0aW9ucyBvZiBEYXRhc2V0IApgYGB7cn0KIyBlaXRoZXIgdmlhIGNvcnB1cyAKcmV2aWV3cy5jb3JwdXMgPC0gY29ycHVzKHJldmlld3MpCmRvY3ZhcnMocmV2aWV3cy5jb3JwdXMsICJsYW5ndWFnZSIpIDwtIHRleHRjYXQocmV2aWV3cy5jb3JwdXMpCnJldmlld3NfZW4gPC0gY29ycHVzX3N1YnNldChyZXZpZXdzLmNvcnB1cywgbGFuZ3VhZ2UgPT0gImVuZ2xpc2giLCBkcm9wX2RvY2lkID0gVFJVRSkKCiMgb3IgdmlhIGRhdGFmcmFtZSBsb2dpYwpyZXZpZXdzLmRmJGxhbmd1YWdlIDwtIHRleHRjYXQocmV2aWV3cy5kZiR0ZXh0KQpgYGAKCiMjIyBTZW50aW1lbnQgQW5hbHlzaXMgb24gUmV2aWV3cyBEYXRhc2V0CgojIyMjIEFGSU5OCmBgYHtyfQojIEFmaW5uCiMgdG9rZW5pemUgCnJldmlld3NfdG9rZW5pemVkIDwtIHJldmlld3NfNTAgJT4lCiAgdW5uZXN0X3Rva2Vucyh0b2tlbnMsIHRleHQpCgojIGFwcGx5IGFmaW5uIGxleGljb24KcmV2aWV3c190b2tlbml6ZWQkYWZpbm4gPC0gdGV4dHN0YXRfdmFsZW5jZShyZXZpZXdzX3Rva2VuaXplZCR0b2tlbnMsIGFmaW5uKSRzZW50aW1lbnQKCiMgcmVwbGFjZSBhbGwgMCB2YWx1ZXMgd2l0aCBuYQpyZXZpZXdzX3Rva2VuaXplZFtyZXZpZXdzX3Rva2VuaXplZCA9PSAwXSA8LSBOQQoKIyBjYWxjdWxhdGUgbWVhbiBzY29yZXMgZm9yIHRva2VucyBwZXIgZG9jCmFmaW5uX3Njb3JlcyA8LSByZXZpZXdzX3Rva2VuaXplZCAlPiUKICBncm91cF9ieShkb2NfaWQpICU+JSAjIGdyb3VwIGRmIGJ5IGRvY19pZCB0byBnZXQgbWVhbiBzZW50aW1lbnQgc2NvcmUKICBzdW1tYXJpc2UodG90YWwgPSBtZWFuKGFmaW5uLCBuYS5ybSA9IFRSVUUpKSAjJT4lICMgY2FsY3VsYXRlIG1lYW4gdy9vIHJlZ2FyZGluZyBuYSB2YWx1ZXMKCiMgYWRkIGFmaW5uIHNjb3JlcyB0byBkZiAKcmV2aWV3c181MCRhZmlubiA8LSBhZmlubl9zY29yZXMkdG90YWwKCiMgZGlmZmVyZW50IHZlcnNpb24gdG8gZ2V0IHBsb3QgCnJldmlld3NfdG9rZW5pemVkICU+JQogIGdyb3VwX2J5KGRvY19pZCkgJT4lICMgZ3JvdXAgZGYgYnkgYm9vayBhbmQgY2hhcHRlciB0byBnZXQgc2VudGltZW50IHBlciBjaGFwdGVyCiAgI3Jldmlld3NfdG9rZW5pemVkJHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkgJT4lCiAgc3VtbWFyaXNlKHNlbnRpbWVudCA9IG1lYW4oYWZpbm4sIG5hLnJtID0gVFJVRSkpICU+JSAjIGNhbGN1bGF0ZSBtZWFuIHcvbyByZWdhcmRpbmcgbmEgdmFsdWVzCiAgbXV0YXRlKG1ldGhvZCA9ICJBRklOTiIpICU+JSAjIGFkZCBjb2x1bW4gd2l0aCBtZXRob2QgCiAgICAgICAgZ2dwbG90KGFlcyhkb2NfaWQsIHNlbnRpbWVudCwgZmlsbCA9IGRvY19pZCkpICsgIyBwbG90IHNlbnRpbWVudCBvZiBib29rcwogICAgICAgICAgZ2VvbV9iYXIoYWxwaGEgPSAwLjgsIHN0YXQgPSAiaWRlbnRpdHkiLCBzaG93LmxlZ2VuZCA9IEZBTFNFKSArCiAgICAgICAgICAjZmFjZXRfd3JhcCh+IGRvY19pZCwgbmNvbCA9IDIsIHNjYWxlcyA9ICJmcmVlX3giKSArCiAgICAgICAgICBnZ3RpdGxlKCJBRklOTiBSZXZpZXdzIikKYGBgCiMjIyMgTEVYSUNPREVSCmBgYHtyfQojIGFwcGx5IGxleGljb2RlciBsZXhpY29uCnJldmlld3NfNTAkbHNkIDwtIHRleHRzdGF0X3BvbGFyaXR5KHRva2VucyhyZXZpZXdzXzUwJHRleHQpLCBkYXRhX2RpY3Rpb25hcnlfTFNEMjAxNSkkc2VudGltZW50IAojc2VyaWVzLmRmIDwtIGFzLmRhdGEuZnJhbWUoc2VyaWVzKQoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgbHNkLCBmaWxsID0gZG9jX2lkKSkgKyAjIHBsb3Qgc2VudGltZW50IG9mIGJvb2tzCiAgICAgICAgICBnZW9tX2JhcihhbHBoYSA9IDAuOCwgc3RhdCA9ICJpZGVudGl0eSIsIHNob3cubGVnZW5kID0gRkFMU0UpICsKICAgICAgICAgICNmYWNldF93cmFwKH4gZG9jX2lkLCBuY29sID0gMiwgc2NhbGVzID0gImZyZWVfeCIpICsKICAgICAgICAgIGdndGl0bGUoIkxleGljb2RlciBSZXZpZXdzIikKcGxvdCAKYGBgCiMjIyMgVmFkZXIKYGBge3J9CnJldmlld3NfNTAkdmFkZXIgPC0gdmFkZXJfZGYocmV2aWV3c181MCR0ZXh0KSRjb21wb3VuZAoKcGxvdCA8LSBnZ3Bsb3QocmV2aWV3c181MCwgYWVzKGRvY19pZCwgdmFkZXIsIGZpbGwgPSBkb2NfaWQpKSArICMgcGxvdCBzZW50aW1lbnQgb2YgYm9va3MKICAgICAgICAgIGdlb21fYmFyKGFscGhhID0gMC44LCBzdGF0ID0gImlkZW50aXR5Iiwgc2hvdy5sZWdlbmQgPSBGQUxTRSkgKwogICAgICAgICAgI2ZhY2V0X3dyYXAofiBkb2NfaWQsIG5jb2wgPSAyLCBzY2FsZXMgPSAiZnJlZV94IikgKwogICAgICAgICAgZ2d0aXRsZSgiVmFkZXIgUmV2aWV3cyIpCnBsb3QgCmBgYAoKYGBge3J9CmdldF9zdGF0aXN0aWNzIDwtIGZ1bmN0aW9uKGRmKSB7CiAgc3RhdGlzdGljcyA8LSBkYXRhLmZyYW1lKG1hdHJpeChuY29sPTQsIG5yb3c9MCkpCiAgeCA8LSBjKCJhY2N1cmFjeSIsICJwcmVjaXNpb24iLCAicmVjYWxsIiwgIkYxIikKICBjb2xuYW1lcyhzdGF0aXN0aWNzKSA8LSB4CiAgbGV4MSA8LSAiYWZpbm5fYmluYXJ5IgogIGxleDIgPC0gImxzZF9iaW5hcnkiCiAgbGV4MyA8LSAidmFkZXJfYmluYXJ5IgogIGdvbGQgPC0gInJhdGluZ19iaW5hcnkiCiAgCiAgbGV4aWNvbnMgPC0gYyhsZXgxLGxleDIsbGV4MykKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGNvbmZ1c2lvbl9tYXRyaXggPC0gdGFibGUoQUNUVUFMPWRmW1tnb2xkXV0sIFBSRURJQ1RFRD1kZltbbGV4XV0pCiAgICBUTiA8LSBjb25mdXNpb25fbWF0cml4WzFdCiAgICBGTiA8LSBjb25mdXNpb25fbWF0cml4WzJdCiAgICBGUCA8LSBjb25mdXNpb25fbWF0cml4WzNdCiAgICBUUCA8LSBjb25mdXNpb25fbWF0cml4WzRdCiAgCiAgICAjIGNhbGN1bGF0ZSBzdGF0aXN0aWNzCiAgICBwcmVjaXNpb24gPC0gVFAvKFRQK0ZQKQogICAgYWNjdXJhY3kgPC0gKFRQK1ROKS8oVFArVE4rRlArRk4pCiAgICByZWNhbGwgPC0gVFAvKFRQK0ZOKQogICAgRjEgPC0gKDIqcHJlY2lzaW9uKnJlY2FsbCkvKHByZWNpc2lvbityZWNhbGwpCiAgCiAgICAjIGFkZCB0byB0YWJsZQogICAgb3V0cHV0IDwtIGMoYWNjdXJhY3kscHJlY2lzaW9uLCByZWNhbGwsIEYxKQogICAgc3RhdGlzdGljc1tsZXgsXSA9IHJiaW5kKHN0YXRpc3RpY3NbW2xleF1dLCBvdXRwdXQpCiAgICB9CiAgICAKICByZXR1cm4oc3RhdGlzdGljcykKICAKfQoKZ2V0X3N0YXRpc3RpY3ModGVzdCkKYGBgCgojIyMgVGVzdCBGdW5jdGlvbnMKYGBge3J9CmBgYHtyfQojIGNvbXBsZXRlIGZ1bmN0aW9uIHRvIGdldCBsZXhpY29uIHNjb3JlcwpnZXRfc2VudGltZW50IDwtIGZ1bmN0aW9uKGRhdGEsIGxleGljb25zKXsKICAKICBmb3IobGV4IGluIGxleGljb25zKXsKICAgIGlmKGxleCA9PSAiYWZpbm4iKXsKICAgICAgCiAgICBkYXRhJGFmaW5uIDwtIHRleHRzdGF0X3ZhbGVuY2UodG9rZW5zKGRhdGEkdGV4dCksIGFmaW5uLCBub3JtYWxpemU9ImRpY3Rpb25hcnkiKSRzZW50aW1lbnQKICAgIH0KICAgIAogICAgaWYobGV4ID09ICJsc2QiKXsKICAgIGRhdGEkbHNkPC0gdGV4dHN0YXRfcG9sYXJpdHkodG9rZW5zKGRhdGEkdGV4dCksIGRhdGFfZGljdGlvbmFyeV9MU0QyMDE1KSRzZW50aW1lbnQKICAgIH0KICAgIAogICAgaWYobGV4ID09ICJ2YWRlciIpewogICAgZGF0YSR2YWRlciA8LSB2YWRlcl9kZihkYXRhJHRleHQpJGNvbXBvdW5kCiAgICB9CiAgfQogIGRhdGFbaXMubmEoZGF0YSldIDwtIDAKICByZXR1cm4oZGF0YSkKfQoKcmV2aWV3c19zZW50aW1lbnQgPC0gZ2V0X3NlbnRpbWVudChyZXZpZXdzLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQp0d2l0dGVyX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHR3aXR0ZXIsIGMoImFmaW5uIiwibHNkIiwgInZhZGVyIikpCnBhcmx2b3RlX3NlbnRpbWVudCA8LSBnZXRfc2VudGltZW50KHBhcmx2b3RlLCBjKCJhZmlubiIsImxzZCIsICJ2YWRlciIpKQoKcGFybHZvdGVfc2VudGltZW50CiN3cml0ZS5jc3YocGFybHZvdGVfc2VudGltZW50LCAiZGF0YXNldHMvcGFybHZvdGVfc2VudGltZW50LmNzdiIsIHJvdy5uYW1lcyA9IEZBTFNFKQpgYGAKYGBgCgoKCgoKCg==